class EventHandler{
	private eventName:string = '';
	private filters:Object = null;
	private callback:Function = null;
	private target:Object = null;
	private once:boolean = null;

	constructor(eventName:string, filters:Object, callback:Function,target:Object, once:boolean){
		this.eventName = eventName;
		this.filters = filters;
		this.callback = callback;
		this.target = target;
		this.once = once;
	}
}

class Jem {
	private static _instance:Jem = null;
	private eventHandlers:Object = {};
	
	public static getInstance():Jem{
		if(this._instance==null){
			this._instance=new Jem();
		}
		return this._instance;
	}

	constructor(){
		this.eventHandlers["*"] =  [];
	}

	private isArray(value:any):Boolean{
		// if(Array.isArray){
		// 	return true;
		// }else{
			return toString.call(value) === '[object Array]';
		// }
	}

	private addHandler(eventName:string, filters:Object, callback:Function,target:Object, once:boolean) {
		if (this.isArray(eventName)) {
			for (var i = 0; i < eventName.length; i++) {
				this.addHandler(eventName[i], filters, callback, target, once);
			}
		} else {
			if (!this.isArray(this.eventHandlers[eventName])) {
				this.eventHandlers[eventName] = [];
			}
			var handler = new EventHandler(eventName, filters, callback, target, once);
			this.eventHandlers[eventName].push(handler);
		}
	}

	private checkFilter(filters:Object, eventProperties:Object) {
		for (var filter in filters) {
			if (filters.hasOwnProperty(filter)) {
				if (!eventProperties || filters[filter] != eventProperties[filter]) {
					return false;
				}
			}
		}
		return true;
	}

	private callHandler(fnHandler:Function, target:Object, eventName:string, eventProperties:Object) {
		if (typeof fnHandler === 'function') {
			try {
				if (!eventProperties) {
					eventProperties = {};
				}
				if(target==null){
					fnHandler(eventName, eventProperties);
				}else{
					fnHandler.apply(target,[eventName, eventProperties]);
				}
			} catch (e) {
				// error occured in event handler. skip this handler.
				if (console && console.error) {
					console.error(e);
				}
			}
		}
	}

	private callHandlers(handlers:any, eventName:string, eventProperties:object) {
		for (var i = 0; i < handlers.length; i++) {
			var handler = handlers[i];
			if ((handler.filters && this.checkFilter(handler.filters, eventProperties)) || !handler.filters) {
				this.callHandler(handler.callback, handler.target, eventName, eventProperties);
				
				if (handler.once === true) {
					handlers.splice(i, 1);
					i--;
				}
			}
		}
	}

	private fireEvent(eventName:string, eventProperties:object) {
		var handlers = this.eventHandlers[eventName];
		if (this.isArray(handlers)) {
			this.callHandlers(handlers, eventName, eventProperties);
		}
		
		var globalHandlers = this.eventHandlers["*"];
		this.callHandlers(globalHandlers, eventName, eventProperties);
	}

	public fire(eventName:string, eventProperties:object, delay?:number, rate?:number) {
		if (typeof delay === "undefined") {
			this.fireEvent(eventName, eventProperties);
		} else {
			if (typeof rate === "undefined") {
				setTimeout(function () {
					this.fireEvent(eventName, eventProperties);
				}, delay);
			} else {
				setTimeout(function () {
					this.fireEvent(eventName, eventProperties);
					setInterval(function () {
						this.fireEvent(eventName, eventProperties);
					}, rate);
				}, delay);
			}
		}
	}

	//target
	public on(...args:any) {
		if (args.length < 2)
			throw new Error("1");
		if (args.length == 2) {
			this.addHandler(args[0], null, args[1], null, false,);
		}else if(args.length == 3){
			this.addHandler(args[0], null, args[1], args[2], false,);
		} else {
			this.addHandler(args[0], args[1], args[2], args[3], false);
		}
	}

	public once(...args:any) {
		if (args.length < 2)
			throw new Error("1");
			
		if (args.length == 2) {
			this.addHandler(args[0], null, args[1], args[2],  true);
		} else {
			this.addHandler(args[0], args[1], args[2], args[3], true);
		}
	}

	public off(eventName:string, callback:Function) {
		if (typeof callback === 'function') {
			// remove only the specified callback
			var handlers = this.eventHandlers[eventName];
			if (this.isArray(handlers)) {
				for (var i = 0; i < handlers.length; i++) {
					var handler = handlers[i];
					if (handler.callback === callback) {
						handlers.splice(i, 1);
						i--;
					}
				}
			}
		} else {
			// no callback was specified. 
			// remove all listeners for the event
			this.eventHandlers[eventName] = [];
		}
	}
}

export {
	Jem
}